package com.jkgh.remedium {
	
	import com.jkgh.logger.Log;
	import com.jkgh.remedium.objects.RemediumMethodObject;
	import com.jkgh.remedium.states.InitializeSystemState;
	import com.jkgh.remedium.states.RemediumState;
	
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.Socket;
	import flash.utils.Dictionary;
	
	public class RemediumSystem {
		
		private var socket:Socket;
		private var state:RemediumState;
		private var myID:int;
		private var objectIDGenerator:int;
		private var callIDGenerator:int;
		private var methodObjects:Object/*RemediumMethodObject*/;
		private var calls:Dictionary/*int, Function*/;
		
		public function RemediumSystem(serverIP:String, mainObjectReceivingState:RemediumState) {
			this.socket = new Socket(serverIP, RemediumConstants.PORT);
			
			socket.addEventListener(Event.CONNECT, onConnected);
			socket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
			socket.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
			socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
			
			this.methodObjects = new Object();
			this.calls = new Dictionary();
			this.objectIDGenerator = 0;
			this.callIDGenerator = 0;
			this.state = new InitializeSystemState(mainObjectReceivingState);
		}
		
		public final function getHostID():int {
			return myID;
		}
		
		public final function setState(state:RemediumState):void {
			this.state = state;
		}
		
		public final function registerCall(onReturn:Function):int {
			var callID:int = nextCallID();
			calls[callID] = onReturn;
			return callID;
		}
		
		public final function getMethodObject(hostID:int, objectID:int):RemediumMethodObject {
			return methodObjects[hash(hostID, objectID)];
		}
		
		public final function registerMethodObject(hostID:int, objectID:int, object:RemediumMethodObject):void {
			methodObjects[hash(hostID, objectID)] = object;
		}
		
		private function hash(hostID:int, objectID:int):String {
			return hostID+","+objectID;
		}
		
		public function nextObjectID():int {
			return objectIDGenerator++;
		}
		
		private function nextCallID():int {
			return callIDGenerator++;
		}
		
		private function onSecurityError(event:SecurityErrorEvent):void {
			Log.error("Problem with policy while connecting: ", event);
		}
		
		private function onIOError(event:IOErrorEvent):void {
			Log.error("Problem when communicating: ", event);
		}
		
		private function onSocketData(event:ProgressEvent):void {
			while (true) {
				var oldState:RemediumState = state;
				state.onBytesAvailable(this);
				if (state == oldState || socket.bytesAvailable == 0) {
					break;
				}
			}
		}
		
		private function onConnected(event:Event):void {
			Log.debug("Connected");
			socket.writeByte(RemediumConstants.REGULAR_CLIENT);
			socket.flush();
		}
		
		public final function bytesAvailable():int {
			return socket.bytesAvailable;
		}
		
		public final function produceByte():int {
			return socket.readByte();
		}
		
		public final function produceInt():int {
			return socket.readInt();
		}
		
		public final function produceDouble():Number {
			return socket.readDouble();
		}
		
		public final function initializeID(myID:int):void {
			this.myID = myID;
		}
		
		public final function consumeInt(i:int):void {
			socket.writeInt(i);
		}
		
		public final function consumeByte(b:int):void {
			socket.writeByte(b);
		}
		
		public final function consumeDouble(d:Number):void {
			socket.writeDouble(d);
		}
		
		public function flush():void {
			socket.flush();
		}
		
		public function retrieveCall(callID:int):Function {
			var ret:Function = calls[callID];
			delete calls[callID];
			return ret;
		}
	}
}